#include <webx/route.h>
#include <openssl/SSLSocket.h>

int port = 465;
int maxtimes = 100;
const char* charset = "utf-8";
const char* pwd = "mailpassword";
const char* host = "smtp.sina.com.cn";
const char* mail = "yourmail@sina.com";
const char* boundary = "--XG-19900606060600991-GX--";

static int SmtpLogout(sp<Socket> sock)
{
	int len = 0;
	char buffer[MAX_PATH];

	if ((len = sock->writeLine("QUIT", 4)) < 0) return len;
	if ((len = sock->readLine(buffer, sizeof(buffer))) < 0) return len;
	if (memcmp(buffer, "221", 3)) return XG_PARAMERR;

	return 0;
}
int SmtpLogin(sp<Socket> sock, const char* usr, const char* pwd)
{
	int len = 0;
	char buffer[MAX_PATH * 4];

	if ((len = sock->writeLine("AUTH LOGIN", 10)) < 0) return len;
	if ((len = sock->readLine(buffer, sizeof(buffer))) < 0) return len;
	if (memcmp(buffer, "334", 3)) return XG_PARAMERR;

	if ((len = strlen(usr)) > 128) return XG_DATAERR;

	len = strlen(BASE64Encode(usr, strlen(usr), buffer, FALSE));

	if ((len = sock->writeLine(buffer, len)) < 0) return len;
	if ((len = sock->readLine(buffer, sizeof(buffer))) < 0) return len;
	if (memcmp(buffer, "334", 3)) return XG_PARAMERR;

	len = strlen(BASE64Encode(pwd, strlen(pwd), buffer, FALSE));

	if ((len = sock->writeLine(buffer, len)) < 0) return len;
	if ((len = sock->readLine(buffer, sizeof(buffer))) < 0) return len;
	if (memcmp(buffer, "235", 3)) return XG_PARAMERR;

	return XG_OK;
}
int SmtpSendComplexHeader(sp<Socket> sock, const char* from, const char* to, const char* title)
{
	int len = 0;
	char buffer[MAX_PATH * 4];

	len = sprintf(buffer, "MAIL FROM: <%s>", from);
	if ((len = sock->writeLine(buffer, len)) < 0) return len;
	if ((len = sock->readLine(buffer, sizeof(buffer))) < 0) return len;
	if (memcmp(buffer, "250", 3)) return XG_PARAMERR;

	len = sprintf(buffer, "RCPT TO: <%s>", to);
	if ((len = sock->writeLine(buffer, len)) < 0) return len;
	if ((len = sock->readLine(buffer, sizeof(buffer))) < 0) return len;
	if (memcmp(buffer, "250", 3)) return XG_PARAMERR;

	if ((len = sock->writeLine("DATA", 4)) < 0) return len;
	if ((len = sock->readLine(buffer, sizeof(buffer))) < 0) return len;
	if (memcmp(buffer, "354", 3)) return XG_PARAMERR;

	len = sprintf(buffer, "FROM: %s\r\nTO: %s\r\nSUBJECT: %s\r\nMIMI-Version:1.0\r\nContent-Type: multipart/mixed;boundary=\"%s\"\r\n\r\nContent-Transfer-Encoding: 7bit\r\n\r\nthis is a mime encoded message\r\n", from, to, title, boundary);
	if ((len = sock->writeLine(buffer, len)) < 0) return len;

	return XG_OK;
}
sp<Socket> SmtpConnect(const char* host, int port, bool crypted, int timeout = SOCKET_CONNECT_TIMEOUT)
{
	int len = 0;
	char buffer[MAX_PATH];
	sp<Socket> sock = crypted ? newsp<SSLSocket>() : newsp<Socket>();

	sock->connect(host, port, timeout);

	if (sock->isClosed() && GetHostAddress(host, buffer)) sock->connect(buffer, port, timeout);

	if (sock->isClosed()) return NULL;

	if ((len = sock->readLine(buffer, sizeof(buffer))) < 0) return NULL;
	if (memcmp(buffer, "220", 3)) return NULL;

	len = sprintf(buffer, "HELO %s", host);
	if ((len = sock->writeLine(buffer, len)) < 0) return NULL;
	if ((len = sock->readLine(buffer, sizeof(buffer))) < 0) return NULL;
	if (memcmp(buffer, "250", 3)) return NULL;

	return sock;
}
int SmtpSendMessage(sp<Socket> sock, const char* from, const char* to, const char* title, const char* msg)
{
	int len = 0;
	char buffer[MAX_PATH * 4];

	len = sprintf(buffer, "MAIL FROM: <%s>", from);
	if ((len = sock->writeLine(buffer, len)) < 0) return len;
	if ((len = sock->readLine(buffer, sizeof(buffer))) < 0) return len;
	if (memcmp(buffer, "250", 3)) return XG_PARAMERR;

	len = sprintf(buffer, "RCPT TO: <%s>", to);
	if ((len = sock->writeLine(buffer, len)) < 0) return len;
	if ((len = sock->readLine(buffer, sizeof(buffer))) < 0) return len;
	if (memcmp(buffer, "250", 3)) return XG_PARAMERR;

	if ((len = sock->writeLine("DATA", 4)) < 0) return len;
	if ((len = sock->readLine(buffer, sizeof(buffer))) < 0) return len;
	if (memcmp(buffer, "354", 3)) return XG_PARAMERR;

	len = sprintf(buffer, "FROM: %s\r\nTO: %s\r\nSUBJECT: %s\r\n", from, to, title);
	if ((len = sock->writeLine(buffer, len)) < 0) return len;
	if ((len = sock->writeLine(msg, strlen(msg))) < 0) return len;
	if ((len = sock->writeLine(".", 1)) < 0) return len;
	if ((len = sock->readLine(buffer, sizeof(buffer))) < 0) return len;
	if (memcmp(buffer, "250", 3)) return XG_PARAMERR;

	return XG_OK;
}
sp<Socket> SmtpLoginConnect(const char* host, int port, bool crypted, const char* usr, const char* pwd, int timeout)
{
	sp<Socket> sock = SmtpConnect(host, port, crypted, timeout);

	if (!sock || SmtpLogin(sock, usr, pwd) < 0) return NULL;

	return sock;
}
int SmtpSendComplexContent(sp<Socket> sock, const char* contype, const char* name, const void* content, int sz, BOOL completed)
{
	int len = 0;
	SmartBuffer tmp;
	SmartBuffer msg;
	char buffer[MAX_PATH * 4];

	len = sprintf(buffer, "--%s\r\nContent-Type: %s;name=%s;\r\nContent-Transfer-Encoding: base64\r\n", boundary, contype, name);

	if ((len = sock->writeLine(buffer, len)) < 0) return len;

	if (sz < 0)
	{
		if (stdx::GetFileContent(tmp, (char*)(content)) <= 0) return XG_IOERR;

		content = tmp.str();
		sz = tmp.size();
	}

	if (msg.malloc(sz * 4 / 3 + 8) == NULL) return XG_SYSERR;

	len = strlen(BASE64Encode(content, sz, msg.str(), FALSE));

	if ((len = sock->writeLine(msg.str(), len)) < 0) return len;

	if (completed)
	{
		len = sprintf(buffer, "--%s--\r\n.", boundary);
		
		if ((len = sock->writeLine(buffer, len)) < 0) return len;

		if ((len = sock->readLine(buffer, sizeof(buffer))) < 0) return len;

		if (memcmp(buffer, "250", 3)) return XG_PARAMERR;
	}

	return XG_OK;
}

class MailApplicaiton : public Application
{
	class MailItem : public WorkItem
	{
	public:
		string id;
		string user;
		string title;
		string content;

		void run()
		{
			int res = 0;
			vector<string> vec;

			try
			{
				stdx::split(vec, user, ",");

				for (string& mail : vec)
				{
					if (webx::IsMailString(mail))
					{
						if ((res = send(mail)) < 0)
						{
							LogTrace(eERR, "send mail[%s][%s][%s] failed[%d]", id.c_str(), mail.c_str(), title.c_str(), res);
						}
						else
						{
							LogTrace(eINF, "send mail[%s][%s][%s] success", id.c_str(), mail.c_str(), title.c_str());
						}
					}
				}
			}
			catch(Exception e)
			{
				LogTrace(eERR, "exception[%s]", e.getErrorString());
			}
		}
		string getTitle() const
		{
			return charset && strcasecmp(charset, "gbk") ? stdx::gbkcode(title) : title;
		}
		int send(const string& user)
		{
			int res = 0;
			sp<Socket> sock = SmtpConnect(host, port, true);

			if (!sock) return XG_NETERR;
			if ((res = SmtpLogin(sock, mail, pwd)) < 0) return res;
			if ((res = SmtpSendComplexHeader(sock, mail, user.c_str(), getTitle().c_str())) == XG_NETERR) return res;
			if ((res = SmtpSendComplexContent(sock, "text/html;charset=UTF-8", "content", content.c_str(), content.length(), true)) == XG_NETERR) return res;

			SmtpLogout(sock);

			return res;
		}
	};

protected:
	MsgQueue mq;

	int sendBatch(const string& grouplist, sp<MailItem> mail)
	{
		string sqlcmd;
		vector<string> vec;
		sp<DBConnect> dbconn = webx::GetDBConnect();
		
		stdx::split(vec, grouplist, ",");

		for (string& group : vec)
		{
			stdx::format(sqlcmd, "SELECT MAIL,GROUPLIST,ENABLED FROM T_XG_USER WHERE GROUPLIST LIKE '%s%%'", group.c_str());

			string tmp;
			sp<RowData> row;
			sp<MailItem> item;
			sp<QueryResult> rs = dbconn->query(sqlcmd);
			
			if (!rs) return XG_SYSERR;

			while (row = rs->next())
			{
				if (row->getInt(2) <= 0) continue;

				if ((tmp = row->getString(1)).length() == group.length() || (tmp.length() > group.length() && tmp[group.length()] == ','))
				{
					item = newsp<MailItem>(*mail);
					item->user = row->getString(0);

					stdx::async(item);
				}
			}
		}

		return 0;
	}
	
public:
	void clean()
	{
		mq.close();
	}
	bool main()
	{
		Process::SetCommonExitSignal();

		CHECK_FALSE_RETURN(webx::InitDatabase());

		string queuename = stdx::format("%s:sendmail_queue", GetCurrentUser());

		if (mq.open(queuename) || mq.create(queuename, 8 * 1024 * 1024))
		{
			ColorPrint(eWHITE, "%s\n", "create mail queue success");
		}
		else
		{
			ColorPrint(eRED, "%s\n", "create mail queue failed");

			return false;
		}

		LogThread::Instance()->init("log");
		TaskQueue::Instance()->start();

		int times = 0;
		bool daily = true;

		while (true)
		{
			SmartBuffer buffer = mq.pop();

			if (buffer.isNull())
			{
				Sleep(100);

				continue;
			}
	
			if (times > maxtimes)
			{
				LogTrace(eIMP, "send mail[%s] limited[%d]", mail, maxtimes);

				Sleep(100);

				continue;
			}

			long deadline;
			time_t now = time(NULL);
			JsonElement json(buffer.str());
			sp<MailItem> item = newsp<MailItem>();

			if (json.isNull()) continue;
			if ((item->id = json["id"].asString()).empty()) continue;
			if ((item->title = json["title"].asString()).empty()) continue;
			if ((item->content = json["content"].asString()).empty()) continue;

			if ((deadline = json["deadline"].asLong()) > 0)
			{
				if (deadline < now)
				{
					LogTrace(eIMP, "mail[%s][%s] timeout", item->id.c_str(), item->title.c_str());

					continue;
				}
			}

			string grouplist = json["grouplist"].asString();

			if ((item->user = json["user"].asString()).length() > 0) stdx::async(item);

			if (grouplist.length() > 0)
			{
				try
				{
					sendBatch(grouplist, item);
				}
				catch(Exception e)
				{
					LogTrace(eERR, "exception[%s]", e.getErrorString());
				}
			}

			DateTime dt(now);

			if (dt.canUse())
			{
				if (dt.hour == 0)
				{
					if (daily)
					{
						daily = false;
						times = 0;
					}
				}
				else
				{
					daily = true;
				}
			}
		}

		clean();

		return true;
	}
};

START_APP(MailApplicaiton)